#define _SCL_SECURE_NO_WARNINGS
#include "stdafx.h"
#include "ASTree.h"
#include "ASTNode.h"
#include "ASTDiffVisitor.h"


namespace AST{

using std::string;

bool compareMatch (LeafMatch first, LeafMatch second)
{
	return (first.sim < second.sim);
}

NodeMatchesMap ASTree::nodeMatches;

//TODO: THIS PREDICATE IS NOT THREAD SAFE!
static std::set<Node*> matchedNodes;

bool noDuplicateLeaves (LeafMatch first, LeafMatch second)
{ 
	if(matchedNodes.find(second.A) != matchedNodes.end() || matchedNodes.find(second.B)!= matchedNodes.end() )
		return true;
	matchedNodes.insert(second.A);
	matchedNodes.insert(second.B);
	return false;
}

//Graphviz graph property writer
struct graph_prop_writer {
    void operator()(std::ostream& out) const {
		out << "graph [rankdir=LR]" << std::endl;
	    out << "node [shape=box, style=filled, fillcolor=white]" << std::endl;
    }
};

class vertex_prop_writer {
public:
	vertex_prop_writer (Graph& graph) : g(graph) {}
//	template <class VertexOrEdge>
	void operator()(std::ostream& out, const vertexDesc& v) const {
		CXCursorKind kind = clang_getCursorKind(g[v].nodeObject->cursor);
		if ( (kind == CXCursor_FunctionDecl) || (kind == CXCursor_CXXMethod) || (kind == CXCursor_Constructor)  )
			out << "[shape=\"doubleoctagon\"]";
		if (!g[v].matchInfo.matched){
				out << "[fillcolor=\"firebrick3\"]";
				out << "[label=\"" <<  g[v].nodeObject->tree->getName() << g[v].nodeObject->id << "\"]";
		}
		else{
			out << "[label=\"" <<  g[v].nodeObject->tree->getName() << g[v].nodeObject->id 
				<< "-" << g[v].matchInfo.matchingNode->tree->getName() <<  g[v].matchInfo.matchingNode->id
				<< std::setiosflags(std::ios::fixed) << "(" << std::setprecision(2) 
				<< g[v].matchInfo.sim << ")\"]";

			if ((g[v].matchInfo.sim< 1.0))
					out << "[fillcolor=\"darkorange2\"]";
			else if ((g[v].matchInfo.someChange) )
					out << "[fillcolor=\"yellow3\"]";
			else
					out << "[fillcolor=\"darkolivegreen3\"]";
		}
		if (g[v].nodeObject->leaf())
			out << "[shape=\"ellipse\"]";
		if (clang_getCursorKind(g[v].nodeObject->cursor) == CXCursor_TranslationUnit)
			out << "[fillcolor=\"gray90\", shape=note]";
    }
private:
	Graph& g;
};

//**************************
//Class: ATree
//**************************
ASTree::ASTree(string ASTname): g() {
	name=ASTname;
	nodeCount=0;
}

string ASTree::getName() const {
	return name;
}

ASTree::~ASTree(void)
{
}

void ASTree::test (){

}

Node* ASTree::addNode(string nodeName, CXCursor cursor){
	vertexProps properties;
	properties.vertexName = nodeName;
	properties.nodeObject = Node::createNode(cursor, nodeCount++, nodeName, this);
	properties.nodeObject->setVertex(add_vertex(properties,g));
	vertexNames.push_back(nodeName);
	return properties.nodeObject;
}

Node* ASTree::addChild(Node* parent, std::string nodeName, CXCursor cursor){
	Node* child = addNode(nodeName, cursor);
	boost::add_edge(parent->getVertex(), child->getVertex(), g);
	return child;
}

//Print  graph to dot file
void ASTree::viewGraph(){
	std::ofstream f(name + ".dot");

	char idNumber [10];

	//boost::dynamic_properties p;
	//p.property("node_id", boost::get(boost::vertex_name, g));
	string* names = new std::string[vertexNames.size()];

	for (unsigned int i=0;i<vertexNames.size();i++){
		sprintf(idNumber, "%d:", i);
		names[i]= std::string(idNumber) + vertexNames[i];
	}
//	boost::write_graphviz(f,g, boost::make_label_writer(names));
	boost::default_writer dfltWriter;
	graph_prop_writer graphPropWriter;
	vertex_prop_writer vertexPropWriter (g);
	boost::write_graphviz(f,g,vertexPropWriter,dfltWriter,graphPropWriter);
	f.close();
	//Convert test.dot file to test.pdf
	std::system((std::string("dot -Tbmp ") + name + ".dot " + " -o " + name + ".bmp ").c_str());
	std::system((std::string("start ") + name + ".bmp").c_str());

}

unsigned long ASTree::getNodeCount() const {
	return nodeCount;
}

ASTree::recurse ASTree::recurseCursor(CXCursor* cursor){
	CXSourceLocation loc;
    CXFile sourceFile;
    unsigned int line, column, offset;
	loc =  clang_getCursorLocation(*cursor);
	clang_getSpellingLocation(loc, &sourceFile, &line, &column, &offset);
	if (sourceFile == NULL)
		return RECURSE;
	const boost::regex e("Projects");
//	const boost::regex f("tchar");
	const char* filename = clang_getCString(clang_getFileName(sourceFile));
	if(boost::regex_search(filename, e))
		return RECURSE;
	else
		return IGNORE;
}

void ASTree::leafs (std::vector<std::string>& leafNames) const {

	for (unsigned i=0; i<g.m_vertices.size();i++){
		if (g.m_vertices[i].m_property.m_value.nodeObject->leaf())
			leafNames.push_back(g.m_vertices[i].m_property.m_value.vertexName);
	}

}

std::vector<std::string> ASTree::diffTrees(ASTree& A, ASTree& B){

	std::vector<std::string> verticesA, verticesB; // REMOVE
	
	MatchList matchList;

	//match leaves in both directions
	matchLeaves(A,B,matchList);
	matchLeaves(B,A,matchList);

	//sort matches based on similarity, from highest to lowest
	matchList.sort(compareMatch);
	matchList.reverse();

	//init list of matched nodes
	matchedNodes.clear();
	matchedNodes.insert(matchList.front().A);
	matchedNodes.insert(matchList.front().B);

	//proceed to remove matches with nodes already matched;
	matchList.unique(noDuplicateLeaves);
	MatchList::iterator mLstIter;

	//TODO: generate list of unmatched nodes

	ASTree::nodeMatches.clear();

	//print matches
	fprintf(stderr, "***UNIQUIFIED MATCH LIST***\n");
	std::ofstream f("diff.txt");
	f << std::string("Leaf Matches:") << std::endl;
	for (mLstIter = matchList.begin(); mLstIter != matchList.end() ; mLstIter++){
		fprintf(stderr, "***Unique:%d:%s matches %d:%s %.2f\n",	(*mLstIter).A->id, 
																(*mLstIter).A->nodeName.c_str(),
																(*mLstIter).B->id, 
																(*mLstIter).B->nodeName.c_str(),
																(*mLstIter).sim);
		f << (*mLstIter).A->tree->getName() << (*mLstIter).A->id << std::string(":") << (*mLstIter).A->nodeName << std::string(" matches ") 
			<< (*mLstIter).B->tree->getName() << (*mLstIter).B->id << std::string(":") << (*mLstIter).B->nodeName.c_str() << std::string(" similarity = "); 
		//f.precision(2);	
		f << std::setiosflags(std::ios::fixed) << std::setprecision(2) << (*mLstIter).sim <<std::endl;
		Match match;
		match.matchingNode = (*mLstIter).B;
		match.sim = (*mLstIter).sim;
		ASTree::nodeMatches[(*mLstIter).A] = match;
		match.matchingNode = (*mLstIter).A;
		ASTree::nodeMatches[(*mLstIter).B] = match;
//		((*mLstIter).A)->tree->g[(*mLstIter).A->getVertex()].matchInfo.matched = true;
//		((*mLstIter).A)->tree->g[(*mLstIter).A->getVertex()].matchInfo.matched = true;

	}
	
	//first pass to initialize tree A
	fprintf(stderr, "***Visiting first tree***\n");
	DiffVisitor innerNodeDiffvisitorA;
	boost::depth_first_search(A.g, boost::visitor(innerNodeDiffvisitorA));

	//second pass to compare B against A
	fprintf(stderr, "***Visiting second tree***\n");
	NodeMatchesMap innerNodeMatches;
	std::set<Node*> changeSet;
	DiffVisitor innerNodeDiffvisitorB(DiffVisitor::VISIT_AND_MATCH_INNER, &innerNodeMatches, &changeSet);
	boost::depth_first_search(B.g, boost::visitor(innerNodeDiffvisitorB));
	
	//third pass to propagate changes up A 
	fprintf(stderr, "***Propagating changes up first tree***\n");
	DiffVisitor changePropagator(DiffVisitor::PROPAGATE_CHANGES, &innerNodeMatches, &changeSet);
	boost::depth_first_search(A.g, boost::visitor(changePropagator));
	
	//print inner node matches
	fprintf(stderr, "***INNER NODE MATCH LIST***\n");
	f << std::string("Inner node matches:") << std::endl;

	//std::ofstream f("diff.txt");
	for (NodeMatchesMap::iterator mLstIter = innerNodeMatches.begin(); mLstIter != innerNodeMatches.end() ; mLstIter++){
		fprintf(stderr, "***Inner node: %d:%s matches %d:%s \n",	(*mLstIter).first->id, 
																(*mLstIter).first->nodeName.c_str(),
																(*mLstIter).second.matchingNode->id, 
																(*mLstIter).second.matchingNode->nodeName.c_str());
		f << (*mLstIter).first->tree->getName() << (*mLstIter).first->id << std::string(":") << (*mLstIter).first->nodeName << std::string(" matches ") 
			<< (*mLstIter).second.matchingNode->tree->getName() << (*mLstIter).second.matchingNode->id << std::string(":") 
			<< (*mLstIter).second.matchingNode->nodeName.c_str() << std::endl;
		//f.precision(2);	
		//f << std::setiosflags(std::ios::fixed) << std::setprecision(2) << (*mLstIter).sim <<std::endl;
	}
	std::vector<Node*> unmatchedNodes;
	for (unsigned int i=0;i<A.g.m_vertices.size();i++){
		if (!(A.g.m_vertices[i].m_property.m_value.matchInfo.matched))
			unmatchedNodes.push_back(A.g.m_vertices[i].m_property.m_value.nodeObject);
	}
	for (unsigned int i=0;i<B.g.m_vertices.size();i++){
		if (!(B.g.m_vertices[i].m_property.m_value.matchInfo.matched))
			unmatchedNodes.push_back(B.g.m_vertices[i].m_property.m_value.nodeObject);
	}
	
	fprintf(stderr, "***Unmatched Nodes***\n");
	f << std::string("Unmatched Nodes:") << std::endl;

	std::vector<Node*>::iterator unMiter;
	for (unMiter= unmatchedNodes.begin(); unMiter != unmatchedNodes.end() ; unMiter++){
		fprintf(stderr, "***Unmatched: %d:%s\n",	(*unMiter)->id, 
													(*unMiter)->nodeName.c_str());
		f << (*unMiter)->tree->getName() << (*unMiter)->id << std::string(":") << (*unMiter)->nodeName << std::endl;
	}

	//Print changed functions and methods
	f << std::string("Changed functions and methods:") << std::endl;
	for (std::set<Node*>::iterator iter=changeSet.begin() ; iter!=changeSet.end();iter++){
		f << (*iter)->tree->getName() << (*iter)->id << ": " << (*iter)->nodeName << std::endl;
	}


	f.close();
	return verticesA;
}

void ASTree::matchLeaves(ASTree& A, ASTree& B, MatchList& matchList){
	LeafMatch goodMatch;
	for (unsigned int i=0; i< A.g.m_vertices.size();i++){
		int id = A.g.m_vertices[i].m_property.m_value.nodeObject->id;
		std::string source = A.g.m_vertices[i].m_property.m_value.nodeObject->nodeSource();
		if (!A.g.m_vertices[i].m_property.m_value.nodeObject->leaf())
			continue; 
		for(unsigned j=0;j<B.g.m_vertices.size();j++){
			if (!B.g.m_vertices[j].m_property.m_value.nodeObject->leaf())
				continue; 
			float sim = A.g.m_vertices[i].m_property.m_value.nodeObject->match(*B.g.m_vertices[j].m_property.m_value.nodeObject);
			if (sim >= 0.3){
				goodMatch.A=A.g.m_vertices[i].m_property.m_value.nodeObject;
				goodMatch.B=B.g.m_vertices[j].m_property.m_value.nodeObject;
				goodMatch.sim=sim;
				matchList.push_back(goodMatch);
			}
		}
	}
}

Graph* ASTree::treeGraph() const{
	return const_cast<Graph*>(&g); //TODO: ouch! put const stuff straight!
}

} //namespace AST